package com.yexx.logger;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Splitter;
import com.yexx.core.constant.CommonConstants;
import com.yexx.core.exception.CommonException;
import com.yexx.core.util.InternetUtil;
import com.yexx.logger.entity.Point;
import com.yexx.logger.monitor.PointUtil;
import com.yexx.uid.IdGenerator;
import com.yexx.uid.SnowFlakeIdGenerator;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static com.yexx.core.constant.CommonConstants.LOG.*;

@Slf4j
@Aspect
@ConditionalOnClass({HttpServletRequest.class, RequestContextHolder.class})
public class ApiLogAspect {

    public ApiLogAspect() {
    }

    IdGenerator idGenerator = new SnowFlakeIdGenerator(0, 0);

    @Value("${spring.application.name}")
    private String applicationName;


    @Autowired
    private ObjectMapper objectMapper;


    @Pointcut("execution(public * com.yexx..*.controller..*.*(..))")
    private void apiAspect() {
    }

    /**
     * 方法执行前
     */
    @Before(value = "apiAspect()")
    public void methodBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String traceId = obtainTranceId(request);
        if (traceId == null) {
            traceId = idGenerator.generate().toString();
            request.setAttribute(TRACE_ID_ATTR, traceId);
        }
        String requestJson = getRequestJson(joinPoint);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Point point = new Point();
        point.setRequestId(traceId);
        point.setApplication(applicationName);
        List<String> declaringTypeNames = Splitter.on(".").splitToList(methodSignature.getDeclaringTypeName());
        point.setClassName(declaringTypeNames.get(declaringTypeNames.size() - 1));
        point.setMethodName(methodSignature.getName());
        point.setUserId(request.getHeader(USER_ID_HEADER));
        point.setParams(requestJson);
        point.setTimestamp(LocalDateTime.now());
        point.setType("request-statistics");
        point.setDevice(assemblyDevice(request));
        //log.debug("[{}] Begin:{}, Request:{}", requestId, request.getServletPath(), requestJson);
        PointUtil.debug(point);
    }

    private String getRequestJson(JoinPoint joinPoint) {
        List<Object> args = new ArrayList();
        for (Object o : joinPoint.getArgs()) {
            if (!(o instanceof HttpServletRequest) && !(o instanceof HttpServletResponse) && !(o instanceof MultipartFile)) {
                args.add(o);
            }
        }
        String requestJson = null;
        try {
            requestJson = objectMapper.writeValueAsString(args);
        } catch (Exception e) {
            log.debug("Can not serialize request params {}", args);
        }
        return requestJson;
    }

    /**
     * 方法执行后
     */
    /*@AfterReturning(returning = "obj", pointcut = "apiAspect()")
    public void methodAfterReturing(JoinPoint joinPoint, Object obj) {
        if (obj == null) return;
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        Object requestId = request.getAttribute(REQUEST_ID_KEY);
        String responseJson = null;
        try {
            responseJson = objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.debug("Can not serialize response data {}", obj);
        }
        log.debug("[{}] After:{}, Response:{}", requestId, request.getServletPath(), responseJson);
    }*/

    /**
     * 方法执行后 异常
     */
    @AfterThrowing(pointcut = "apiAspect()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        if (e == null) return;
        CommonException ce = (CommonException) e;
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String traceId = obtainTranceId(request);
        String requestJson = getRequestJson(joinPoint);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Point point = new Point();
        point.setRequestId(traceId);
        point.setApplication(applicationName);
        List<String> declaringTypeNames = Splitter.on(".").splitToList(methodSignature.getDeclaringTypeName());
        point.setClassName(declaringTypeNames.get(declaringTypeNames.size() - 1));
        point.setMethodName(methodSignature.getName());
        point.setUserId(request.getHeader(USER_ID_HEADER));
        point.setParams(requestJson);
        point.setCode(ce.getCode());
        point.setMsg(ce.getMessage());
        point.setTimestamp(LocalDateTime.now());
        point.setType("request-throwing");
        point.setDevice(assemblyDevice(request));
        PointUtil.debugExp(point);
    }

    private String obtainTranceId(HttpServletRequest request) {
        String traceId = null;
        String traceIdHeader = request.getHeader(TRACE_ID_HEADER);
        String traceIdAttr = (String) request.getAttribute(CommonConstants.LOG.TRACE_ID_ATTR);
//        String s = MDC.get(LOG_TRACE_ID); //从日志MDC中也可以拿到
        if (!StringUtils.isEmpty(traceIdHeader)) {
            traceId = traceIdHeader;
        }
        if (!StringUtils.isEmpty(traceIdAttr)) {
            traceId = traceIdAttr;
        }
        return traceId;
    }

    //device格式
    private String assemblyDevice(HttpServletRequest request) {
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        return String.format("ip=%s&browser=%s&os=%s"
                , InternetUtil.getRemoteAddr(request)
                , InternetUtil.getBrowser(userAgent.getBrowser().getName())
                , InternetUtil.getOperatingSystem(userAgent.getOperatingSystem().getName())
        );
    }

}
